package com.fly.git.service;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.TimeUnit;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.fly.git.common.exception.ValidateException;
import com.fly.git.common.utils.HttpRequestUtils;

import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * GitClientService
 * 
 * @author 00fly
 * @version [版本号, 2021年6月27日]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@Setter
@Service
public class GitClientService
{
    private String remoteGit;
    
    private String localPath;
    
    private String userName;
    
    private String passWord;
    
    private String email;
    
    private UsernamePasswordCredentialsProvider credentialsProvider;
    
    private int startInclusive = 0;
    
    private int endExclusive = 24;
    
    private Set<Integer> runHours = new ConcurrentSkipListSet<>();
    
    private Long lastRunTime;
    
    public GitClientService()
    {
        super();
        try
        {
            localPath = new File("project").getCanonicalPath();
        }
        catch (IOException e)
        {
        }
    }
    
    /**
     * 克隆远程库
     * 
     * @throws InvalidRemoteException
     * @throws TransportException
     * @throws GitAPIException
     * @see [类、类#方法、类#成员]
     */
    public void gitClone()
        throws InvalidRemoteException, TransportException, GitAPIException
    {
        Assert.isTrue(StringUtils.isNoneEmpty(userName, passWord, email, remoteGit), "git params is empty");
        credentialsProvider = new UsernamePasswordCredentialsProvider(userName, passWord);
        CloneCommand cloneCommand = Git.cloneRepository();
        cloneCommand.setURI(remoteGit).setBranch("master").setCredentialsProvider(credentialsProvider);
        cloneCommand.setDirectory(new File(localPath));
        try (Git git = cloneCommand.call())
        {
            log.info("gitClone success!  tag: {}", git.tag());
        }
    }
    
    /**
     * 本地提交代码
     */
    public void localCommit()
        throws IOException, GitAPIException, JGitInternalException
    {
        Assert.isTrue(StringUtils.isNoneEmpty(userName, passWord, email, remoteGit), "git params is empty");
        credentialsProvider = new UsernamePasswordCredentialsProvider(userName, passWord);
        try (Git git = new Git(new FileRepository(localPath + "/.git")))
        {
            String remark = DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss") + " auto commit.";
            git.commit().setAmend(true).setAuthor(userName, email).setMessage(remark).call();
        }
    }
    
    /**
     * 拉取远程仓库内容到本地
     */
    public void remotePull()
        throws IOException, GitAPIException
    {
        Assert.isTrue(StringUtils.isNoneEmpty(userName, passWord, email, remoteGit), "git params is empty");
        credentialsProvider = new UsernamePasswordCredentialsProvider(userName, passWord);
        try (Git git = new Git(new FileRepository(localPath + "/.git")))
        {
            git.pull().setRemote("origin").setRemoteBranchName("master").setCredentialsProvider(credentialsProvider).call();
        }
    }
    
    /**
     * push本地代码到远程仓库地址
     */
    public void remotePush()
        throws IOException, JGitInternalException, GitAPIException
    {
        Assert.isTrue(StringUtils.isNoneEmpty(userName, passWord, email, remoteGit), "git params is empty");
        credentialsProvider = new UsernamePasswordCredentialsProvider(userName, passWord);
        try (Git git = new Git(new FileRepository(localPath + "/.git")))
        {
            git.push().setRemote("origin").setCredentialsProvider(credentialsProvider).call();
        }
    }
    
    /**
     * 定时任务调度逻辑
     * 
     * @see [类、类#方法、类#成员]
     */
    @Scheduled(cron = "0 0 0/1 * * ?")
    public void schedule()
    {
        // 设置代码提交小时[0,24)集合
        int curHour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        if (runHours.isEmpty() || curHour == 0)
        {
            registerHours(startInclusive, endExclusive);
        }
        log.info("config runHours is {}", runHours);
        
        // 判断是否达到运行条件
        if (!runHours.contains(curHour))
        {
            log.info("now is {}, it is time to sleep ......", curHour);
            return;
        }
        try
        {
            long seconds = RandomUtils.nextLong(0, 600L);
            log.info("★★★★ Thread will sleep seconds： {}", seconds);
            TimeUnit.SECONDS.sleep(seconds);
            runGitAll();
        }
        catch (InterruptedException | IOException | GitAPIException e)
        {
            log.error("schedule error", e.getCause());
        }
    }
    
    /**
     * 返回设定运行时间点
     * 
     * @param startInclusive 开始小时（包含）
     * @param endExclusive 结束小时（排除）
     * @see [类、类#方法、类#成员]
     */
    public void registerHours(int startInclusive, int endExclusive)
    {
        this.startInclusive = startInclusive;
        this.endExclusive = endExclusive;
        runHours.clear();
        int count = RandomUtils.nextInt(2, 3 + (endExclusive - startInclusive) / 5);
        while (runHours.size() < count) // 小心避免此处无限循环
        {
            runHours.add(RandomUtils.nextInt(startInclusive, endExclusive));
        }
    }
    
    /**
     * 全流程
     * 
     * @throws IOException
     * @throws GitAPIException
     * @throws InterruptedException
     * 
     * @see [类、类#方法、类#成员]
     */
    public void runGitAll()
        throws IOException, GitAPIException
    {
        Assert.isTrue(StringUtils.isNoneEmpty(userName, passWord, email, remoteGit), "git params is empty");
        credentialsProvider = new UsernamePasswordCredentialsProvider(userName, passWord);
        
        // 仅限制http接口请求，不影响定时任务
//        if (HttpRequestUtils.getHttpServletRequest() != null)
//        {
//            if (lastRunTime != null && System.currentTimeMillis() < lastRunTime + 600000)
//            {
//                String msg = String.format("您请求过于频繁，请%s后再试", DateFormatUtils.format(lastRunTime + 600000, "HH:mm:ss"));
//                throw new ValidateException(msg);
//            }
//            lastRunTime = System.currentTimeMillis();
//        }
        log.info("it is time to ready run ......");
        if (!new File(localPath).exists())
        {
            gitClone();
        }
        
        // 替换最后一行内容
        File readMe = new File("project/Modify.md");
        List<String> lines = FileUtils.readLines(readMe, "UTF-8");
//        if (lines.size() > 1)
//        {
//            lines.remove(lines.size() - 1);
//        }
        lines.add(DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"));
        FileUtils.writeLines(readMe, "UTF-8", lines);
        
        File rootDir = new File("project");
        try (Git git = Git.open(rootDir))
        {
            List<String> files = Arrays.asList(Objects.requireNonNull(rootDir.list((dir, name) -> !name.endsWith(".git"))));
            List<DiffEntry> diffEntries = git.diff().setPathFilter(PathFilterGroup.createFromStrings(files)).setShowNameAndStatusOnly(true).call();
            if (diffEntries == null || diffEntries.isEmpty())
            {
                return;
            }
            // 被修改过的文件
            List<String> updateFiles = new ArrayList<>();
            ChangeType changeType;
            for (DiffEntry entry : diffEntries)
            {
                changeType = entry.getChangeType();
                switch (changeType)
                {
                    case ADD:
                    case COPY:
                    case RENAME:
                    case MODIFY:
                        updateFiles.add(entry.getNewPath());
                        break;
                    case DELETE:
                        updateFiles.add(entry.getOldPath());
                        break;
                }
            }
            
            // 将文件提交到git仓库中，并返回本次提交的版本号
            // 1、将工作区的内容更新到暂存区
            AddCommand addCmd = git.add();
            for (String file : updateFiles)
            {
                addCmd.addFilepattern(file);
            }
            addCmd.call();
            
            // 2、commit
            CommitCommand commitCmd = git.commit();
            for (String file : updateFiles)
            {
                commitCmd.setOnly(file);
            }
            String remark = DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss") + " auto commit.";
            RevCommit revCommit = commitCmd.setCommitter(userName, email).setMessage(remark).call();
            log.info("★★★★ Local Commit successful： {}", revCommit.getName());
            
            // swagger测试忽略远程提交
//            if (HttpRequestUtils.getHttpServletRequest() != null)
//            {
//                log.info("★★★★ Swagger HttpServletRequest Test, not call remote git push ★★★★ ");
//                return;
//            }
            
            // 3、git push
            log.info("★★★★ now call remote git push ★★★★");
            PushCommand push = git.push();
            push.setRemote("origin").setCredentialsProvider(credentialsProvider).call();
        }
    }
    
    /**
     * autoCommitPull
     * 
     * @return
     * @see [类、类#方法、类#成员]
     */
    // @Scheduled(cron = "0 0 9-17 * * ?")
    public boolean autoCommitPull()
    {
        try
        {
            log.info("★★★★ autoCommitPull ★★★★");
            if (!new File(localPath).exists())
            {
                gitClone();
            }
            localCommit();
            remotePush();
            return true;
        }
        catch (JGitInternalException | GitAPIException | IOException e)
        {
            log.error("autoCommitPull error", e.getCause());
            return false;
        }
    }
    
    public Set<Integer> getRunHours()
    {
        return runHours;
    }
}
